//
// Created by martin on 8/3/21.
//

#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <error.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <string.h>
#include <net/if.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <event2/buffer.h>

#include "socket.h"


/**
 * create a socket for server to listening connection request
 * @param ip ipv4 address string
 * @param port port number
 * @return a socket fd binded IP & port when success; or -1 when failed.
 * @note server use socket(), bind(), listen()
 */
int socket_create(const char *ip, int port)
{
    if (ip == NULL || port < 0) {
        return -1;
    }

    /* create socket */
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket error");
        return -1;
    }

    /* set sock option SO_REUSEADDR for re-bind when calling socket failed */
    int op = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op));

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip);

    if (bind(sock, (SA *)&local, sizeof (local)) < 0) {
        return -1;
    }

    if (listen(sock, 10) < 0) {
        return -1;
    }

    return sock;
}

/**
 * create a socket bind IP & port for client
 * @param ip ipv4 address string
 * @param port port number
 * @return a socket fd binded IP & port when success; or -1 when failed.
 * @note client use socket(), bind(), but do not use listen() to create a socket to connect to a server.
 */
int socket_client_create_bind(const char *ip, int port)
{
    if (ip == NULL || port < 0) {
        return -1;
    }

    /* create socket */
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket error");
        return -1;
    }

    /* set sock option SO_REUSEADDR for re-bind when calling socket failed */
    int op = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &op, sizeof(op));

    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port);
    local.sin_addr.s_addr = inet_addr(ip);

    if (bind(sock, (SA *)&local, sizeof (local)) < 0) {
        return -1;
    }

    return sock;
}

int socket_accept(int sock)
{
    struct sockaddr_in peer;
    socklen_t len = sizeof(peer);

    int connfd = accept(sock, (SA *)&peer, &len);
    if (connfd < 0) {
        return -1;
    }

    return connfd;
}

int socket_connect(const char *ip, int port)
{
    if (ip == NULL || port < 0) {
        return -1;
    }

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        return -1;
    }

    struct sockaddr_in peer;
    peer.sin_family = AF_INET;
    peer.sin_port = htons(port);
    peer.sin_addr.s_addr = inet_addr(ip);

    if (connect(sock, (SA *)&peer, sizeof(peer)) < 0) {
        return -1;
    }

    return sock;
}


/**
 * 获取本地ip地址, 排除"127.0.0.1"
 * @note ioctl非POSIX.1内容, 各系统对其实现也不尽相同, 不同系统上实现可能不一样.
 * 方法是通过ioctl获取本地mac地址的配置信息, 然后将地址信息
 */
int socket_getLocalIPAddr(char *ipbuf)
{
    int sockfd;
    struct ifconf ifconf;
    struct ifreq *ifreq;
    char buf[512];
    char *ip;
    struct sockaddr_in *addrp;
    int i;

    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
        perror("socket failed");
        return -1;
    }

    ifconf.ifc_len = sizeof(buf);
    ifconf.ifc_buf = buf;
    ioctl(sockfd, (int)SIOCGIFCONF, &ifconf); /* 获取所有接口信息 */

    close(sockfd);

    ifreq = (struct ifreq *)buf;
    for (i = (ifconf.ifc_len / (int)sizeof(struct ifreq)); i > 0; --i) {
        addrp = (struct sockaddr_in *)&(ifreq->ifr_addr);
        ip = inet_ntoa(addrp->sin_addr); /* ip -> static buf */

        if (strcmp(ip, "127.0.0.1") == 0) { /* 排除127.0.0.1 */
            ++ifreq;
            continue;
        }

        if (ipbuf != NULL) {
            strcpy(ipbuf, ip);
        }
        ++ifreq;
    }

    return 0;
}

/**
 * 发送FTP应答
 * @param bev bufferevent对象
 * @param code FTP应答码
 * @param text FTP应答文本，注意不能是指向栈的指针
 */
void send_reply(struct bufferevent *bev, int code, const char *text)
{
    if (!bev) return;
    printf("ResponseCMD: %d %s\n", code, text);

    evbuffer_add_printf(bufferevent_get_output(bev), "%d %s\r\n", code, (NULL == text)? "": text);
}

void send_data(struct bufferevent *bev, const char *data)
{
    if (!bev || !data) return;
    bufferevent_write(bev, data, strlen(data));
    bufferevent_flush(bev, EV_WRITE, BEV_NORMAL); // necessary?
}

